import java.util.LinkedList ;
import java.util.Vector ;

// IParameters requires a child class to implement getParam ( String ) and
// setParam ( String , String )
public abstract class SkeletalNeuronFramework extends ParameterFramework implements INeuron , IGraphable {
    protected int time = 0 ;
    protected Point pt = null ;
    protected LinkedList<ISynapse> syn = new LinkedList<ISynapse> ( ) ;
    private Vector<Double> potential = new Vector<Double> ( 1000 , 1000 ) ;
    private Vector<Double> times = new Vector<Double> ( 1000 , 1000 ) ;
    protected IAppliedCurrent Iapp = new ConstantCurrent( 0 ) ;
    protected double lastSpike = Double.NEGATIVE_INFINITY ;
    private int dataCount = 0 ;
    private static boolean hasSetNaN = false , hasGotNaN = false ;
    public int MAX_DATA_POINTS = 100000 ;
    
    public double getStrengthOfSynapseTo( INeuron n ) {
        return n.getStrengthOfSynapseFrom( this ) ;
    }
    
    public double getStrengthOfSynapseFrom( INeuron n ) {
        double result = 0 ;
        for ( ISynapse s : syn ) {
            if ( s.getPreCell( ) == n ) {
                result += s.strength( ) ;
            }
        }
        return result ;
    }
    
    public boolean hasSynapseTo( INeuron n ) {
        return n.hasSynapseFrom( this ) ;
    }
    
    public boolean hasSynapseFrom( INeuron n ) {
        for ( ISynapse s : syn ) {
            if ( s.getPreCell( ) == n ) {
                return true ;
            }
        }
        return false ;
    }
    
    public void clearHistory( ) {
        double v = getPotential( ) ;
        potential = new Vector<Double> ( 1000 , 1000 ) ;
        setPotential( time , v ) ;
    }
    
    public abstract INeuron clone( ) ;
    
    public IAppliedCurrent getAppliedCurrent( ) {
        return Iapp ;
    }
    
    /**
     * Consolidates the storage of potential history.
     *
     * Throws out all data before time-50 except at integer times. Also, doesn't work.
     */
    private void consolidate( ) {
        double t;
        int i = 0 , j = 0 ;
        double t2 = Double.NEGATIVE_INFINITY ;
        // i = position in existing potential
        // j = position in new potential
        // t2 = time last stored
        for ( i = 0 ; i < dataCount ; i ++ ) {
            t = times.get(i).doubleValue( ) ;
            if ( t > t2 + .9 || t > time - 50 ) {
                t2 = t ;
                times.set(j, times.get(i));
                potential.set(j, potential.get(i));
                j ++ ;
            }
        }
        dataCount = j ;
    }
    
    
         /*
    private void consolidate ( ) {
        Vector<Double> potential = new Vector<Double> ( 1000 , 1000 ) ;
        Vector<Double> times = new Vector<Double> ( 1000 , 1000 ) ;
        int i , j , k ;
        int total ;
        if ( time > 50 ) {
            j = locate( time - 50 ) ;
            total = time + dataCount - j ;
            potential.setSize(total);
            times.setSize(total);
            for ( i = 0 ; i < time - 50 ; i ++ ) {
                times.set(i,new Double(i));
                potential.set(i,new Double(getPotential((double) i)));
            }
            for ( k = time - 50 ; j < dataCount ; j ++ , k ++ ) {
                times.set(k,this.times.get(j));
                potential.set(k,this.potential.get(j));
            }
            this.times = times ;
            this.potential = potential ;
            System.out.println("Consolidated.  Old size: " + dataCount + "  new: " + (k-1));
            this.dataCount = k - 1;
        }
    } */
    
    private int locate( double t ) {
        int i , t0 ;
        int jump = ( int ) ( dataCount  / 2 ) ;
        boolean done = false;
        double tNow ;
        
        i = jump ;
        
        do {
            if ( jump <= 1 ) {
                done = true ;
            }
            jump = ( jump + 1 ) / 2 ;
            tNow = times.get(i).doubleValue();
            if ( tNow < t ) {
                t0 = i ;
                i += jump ;
                if ( i > dataCount - 1 ) {
                    i = dataCount - 1 ;
                }
            } else if ( tNow > t ) {
                i -= jump ;
                t0 = i ;
                if ( i < 0 ) {
                    i = 0 ;
                }
            } else {
                return i;
            }
        } while ( !done ) ;
        return t0;
    }
    
    // TODO:  Currently assumes times are stored in increasing order
    //        but nothing enforces this
    public double getPotential( double t ) {
        int t0 = -1 ;
        
        double tLeft , tRight , vLeft , vRight , frac ;
        try {
            if ( t < times.get(0).doubleValue() || t > times.get(dataCount-1).doubleValue() ) {
                return 0 ;
            }
        } catch ( Exception noDataYet ) {
            return 0 ;
        }
        t0 = locate( t ) ;
        
        // t0 is set such that times.get(t0) < t
        if ( t0 == dataCount - 1 || t0 < 0 ) {
            return 0 ;
        }
        
        tLeft = times.get(t0).doubleValue() ;
        vLeft = potential.get(t0).doubleValue() ;
        if ( tLeft == t ) {
            return vLeft ;
        }
        tRight = times.get(t0+1).doubleValue() ;
        vRight = potential.get(t0+1).doubleValue() ;
        if ( tRight == tLeft ) {
            return vLeft ;
        }
        frac = ( t - tLeft ) / ( tRight - tLeft ) ;
        return frac * vRight + ( 1 - frac ) * vLeft ;
    }
    
    public double getPotential( int t ) {
        return getPotential( ( double ) t ) ;
    }
    
    public double getPotential( ) {
        try {
            return potential.get(dataCount - 1).doubleValue();
        } catch ( Exception e ) {
            return 0 ;
        }
    }
    
    public double lastSpike( ) {
        return lastSpike ;
    }
    
    public void setAppliedCurrent( IAppliedCurrent i ) {
        if ( i == null ) {
            i = new ConstantCurrent( 0 ) ;
        }
        Iapp = i ;
    }
    
    public void addSynapse( ISynapse s ) {
        syn.add( s ) ;
    }
    
    public int getNumSynapses( ) {
        return syn.size( ) ;
    }
    
    public LinkedList<ISynapse> getSynapses( ) {
        return new LinkedList<ISynapse> ( syn ) ;
    }
    
    public void setPlace( Point p ) {
        pt = p ;
    }
    
    public Point getPlace( ) {
        return pt ;
    }
    
    public double dist( INeuron n ) {
        Point p = n.getPlace( ) ;
        if ( p == null || pt == null ) return Double.POSITIVE_INFINITY ;
        return p.distTo( pt ) ;
    }
    
    public void setTime( int t ) {
        double v = getPotential( ) ;
        time = t ;
        setPotential( time , v ) ;
    }
    
    public int getTime( ) {
        return time ;
    }
    
    public void advanceSynapses( ) {
        for ( ISynapse s : syn ) {
            s.advance( ) ;
        }
    }
    
    public void setPotential( double v ) {
        setPotential( time , v ) ;
    }
    
    public void setPotential( double t , double v ) {
        if ( Double.isNaN( v ) && ( !hasSetNaN ) )  {
            hasSetNaN = true ;
            System.out.println( "Setting NaN" ) ;
        }
        if ( t == 0 ) {
            if ( dataCount == 0 ) {
                dataCount = 1 ;
                potential.setSize( 1000 ) ;
                times.setSize( 1000 ) ;
            }
            potential.set(0,new Double(v));
            times.set(0,new Double( t ) ) ;
        } else {
            if ( dataCount >= potential.size( ) ) {
                potential.setSize( dataCount + 1000 ) ;
                times.setSize( dataCount + 1000 ) ;
            }
            potential.set( dataCount , new Double( v ) ) ;
            times.set( dataCount , new Double( t ) ) ;
            dataCount ++ ;
            if ( dataCount > MAX_DATA_POINTS ) {
                consolidate();
            }
        }
    }
    
    public Curve getCurve( ) {
        double [ ] t = new double [ time + 1 ] ;
        double [ ] y = new double [ time + 1 ] ;
        int i ;
        for ( i = 0 ; i <= time ; i ++ ) {
            t [ i ] = i ;
            y [ i ] = getPotential( i ) ;
        }
        try {
            return new Curve( t , y , null ) ;
        } catch ( Exception ex ) {
            return null ;
        }
    }
    
    public Curve getCurve( int t1 , int t2 ) {
        int tstart = Math.min( t1 , t2 ) ;
        int tstop = Math.max( t1 , t2 ) ;
        int i ;
        int tsteps = tstop - tstart ;
        double [ ] t = new double [ tsteps + 1 ] ;
        double [ ] y = new double [ tsteps + 1 ] ;
        
        for ( i = 0 ; i <= tsteps ; i ++ ) {
            t [ i ] = i + tstart ;
            y [ i ] = getPotential( i + tstart ) ;
        }
        try {
            return new Curve( t , y , null ) ;
        } catch ( Exception ex ) {
            return null ;
        }
    }
}
